module DrWho where

import qualified Data.ByteString as ByteString
import qualified Data.Map as Map
import Data.Maybe (fromJust)
import System.Environment (getArgs)
import System.Directory (renameFile)
import System.IO (hSetBuffering, stdin, stdout, BufferMode (..))
import Prelude hiding (div, span)
import qualified Data.Yaml as Yaml

import Data
import HTML
import IntroOutro

data Compact = Compact | Normal deriving Eq

--------------------------------------------------------------------------------
-- Output functions
--------------------------------------------------------------------------------
preamble, postamble, tableHeading :: String
preamble = "<html lang=\"en\"><head><title>Avery's Doctor Who Guide</title><meta charset=\"utf-8\" /><link rel=\"stylesheet\" href=\"style.css\"></head><body>"
postamble = "</body></html>"
tableHeading = "<table class=\"maintable\"><tr><th>Story</th><th>Watch?</th><th>Details</th></tr>\n"

output :: Compact -> Table -> String
output compact table = preamble +. introduction +. toc table +. "<hr>" +. output' compact table Nothing +. outro +. postamble
  where
    output' _ [] _ = ""
    output' compact (Doctor n description seasons : rest) previousDoctor
      = "<a name=\"doctor" ++ show n ++ "\"></a>"
      +. h1 (ordinal n ++ " Doctor") ++ img ("../images/doctor-who/doctor" ++ show n ++ ".png") ("The " ++ ordinal n ++ " Doctor")
      +. p' "nav" (
        (case previousDoctor of
            Just doctor -> span "nav-link" (a ("#doctor" ++ show doctor) "⮴ previous doctor")
            Nothing     -> span "nav-link" "⮴ previous doctor"
        )
        +. (if not (null rest)
            then span "nav-link" (a ("#doctor" ++ show (n + 1)) "⮶ next doctor")
            else span "nav-link" "⮶ next doctor"
           )
        +. span "nav-link" (a "#top" "⮬ back to top")
      )
      +. div "details-text" description
      +. tableHeading +. outputSeasons compact seasons +. "</table>"
      +. output' compact rest (Just n)

outputSeasons :: Compact -> [Season] -> String
outputSeasons compact s = outputSeasons' compact s Nothing
  where
    outputSeasons' _ [] _ = ""
    outputSeasons' compact [season] prevSeason = outputSeason compact season (prevSeason, Nothing)
    outputSeasons' compact (seasonA : seasonB : rest) prevSeason
      = let thisSeason = Just (seasonNum seasonA)
            nextSeason = Just (seasonNum seasonB)
        in outputSeason compact seasonA (prevSeason, nextSeason)
           ++ outputSeasons' compact (seasonB : rest) thisSeason

outputSeason :: Compact -> Season -> (Maybe SeasonNum, Maybe SeasonNum) -> String
outputSeason compact (Season num stories) (previousSeason, nextSeason)
  = tr' "season" ("<td colspan=3>" ++ "<a name=\"season-" ++ num ++ "\"></a>"
                  +. h3 ("Season " ++ num)
                  +. p' "nav" (
                     (case previousSeason of
                          Just season -> span "nav-link" (a ("#season-" ++ season) "previous season ⬏")
                          Nothing     -> span "nav-link" "previous season ⬏"
                     )
                     +. (case nextSeason of
                            Just season -> span "nav-link" (a ("#season-" ++ season) "next season ⬎")
                            Nothing     -> span "nav-link" "next season ⬎"
                     )
                  )
                  +. "</td>")
    +. concatMap (outputStory compact) stories

outputStory :: Compact -> Story -> String
outputStory compact (Story name number numEps missing recc note synopsis review majorPlotChanges)
  = let (nameClass, reconstruction)
          = case missing of
              None   -> ("name", "")
              Some _ -> ("name-missing", div "reconstruction-partial" partialReconstruction)
              All    -> ("name-missing", div "reconstruction" fullReconstruction)
        partialReconstruction = "<span title=\"Some of the episodes are missing, and had to be reconstructed. Usually still pretty watchable\">" ++ "Partial Reconstruction" ++ "</span>"
        fullReconstruction = "<span title=\"All of the episodes are missing, and had to be reconstructed from stills.\">" ++ "Full Reconstruction" ++ "</span>"
        infoTable
          = simplehtml "table"
             (  tr' "info" (td' "info-tag" "Story Number" ++ td (show number))
             +. tr' "info" (td' "info-tag" "Number of Episodes" ++ td (show numEps))
             +. case missing of
                 None     -> ""
                 All      -> tr' "info" (td' "info-tag" "Missing Episodes" ++ td "All")
                 Some eps -> tr' "info" (td' "info-tag" "Missing Episodes" ++ td (showEps eps))
             )
        detailsTable
          = styledhtml "table" "details"
              (  tr' "details" (td' "details-tag" "Synopsis" ++ td' "details-text" synopsis)
              +. tr' "details" (td' "details-tag" "Review"   ++ td' "details-text" review)
              +. (case majorPlotChanges of
                    Just changes -> tr' "details" (td' "details-tag" "Major Plot Changes"
                                                   ++ td' "details-text"
                                                   (simplehtml "details" $
                                                    simplehtml "summary" "Show/hide" ++ p changes
                                                   )
                                                  )
                    Nothing -> ""
             ))
  in tr' "name"
    (td' nameClass
        ("<p class=" ++ nameClass ++ ">" ++ name ++ "</p>"
         ++ reconstruction
         ++ if compact == Compact then "" else infoTable
        )
    +. td (div (show recc)
            (
              (case recc of
                  Highly -> "✨ Highly Recommended ✨"
                  Yes    -> "Watch"
                  Maybe  -> "Maybe"
                  Partial-> "Partial watch"
                  No     -> "Don't watch"
              )
              ++ (case note of
                     Just text -> ", " ++ text
                     Nothing   -> ""
                 )
            )
          )
    +. td (if compact == Compact then "" else detailsTable))
    ++ "\n"

showEps :: [Int] -> String
showEps []  = "None"
showEps [e] = "Only episode " ++ show e
showEps eps = showEps' eps
  where
    showEps' [e1, e2] = show e1 ++ ", and " ++ show e2
    showEps' (e:rest) = show e ++ ", " ++ showEps' rest

ordinal :: Int -> String
ordinal n = case n of
              1  -> "First"
              2  -> "Second"
              3  -> "Third"
              4  -> "Fourth"
              5  -> "Fifth"
              6  -> "Sixth"
              7  -> "Seventh"
              8  -> "Eighth"
              9  -> "Nineth"
              10 -> "Tenth"
              11 -> "Eleventh"
              12 -> "Twelfth"
              13 -> "Thirteenth"
              14 -> "Fourteenth"
              15 -> "Fifteenth"
              16 -> "Sixteenth"
              17 -> "Seventeenth"
              18 -> "Eighteenth"
              19 -> "Nineteenth"
              _  -> error "I haven't accounted for this many doctors"

toc :: Table -> String
toc [] = ""
toc table = div "dimbox" $
                "<details>" +. simplehtml "summary" "Table of Contents"
                +. "<ol>" +. toc' table +. "</ol>"
                +. "</details>"
  where
    toc' [] = ""
    toc' (Doctor n _ seasons : rest) = li $ a ("#doctor" ++ show n) (ordinal n ++ " Doctor")
                                     +. simplehtml "ul" (tocSeasons seasons)
                                     +. toc' rest
    tocSeasons [] = ""
    tocSeasons (Season n _ : rest) = li $ a ("#season-" ++ n) ("Season " ++ n)
                                     +. tocSeasons rest

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
data Statistics = Statistics
                  { storiesWatched  :: Int
                  , episodesWatched :: NumEpisodes
                  , episodesRecommended :: NumEpisodes
                  , numRecommended  :: Map.Map Recommendation Int
                  }

instance Show Statistics where
  show soFar = "----------------\n"
            ++ "\n"
            ++ "Stories Watched:  " ++ show (storiesWatched soFar) ++ "\n"
            ++ "Episodes Watched: " ++ show (episodesWatched soFar) ++ "\n"
            ++ "\n"
            ++ "----------------\n"
            ++ "\n"
            ++ "Story Recommendations:\n"
            ++ "          Highly: " ++ show (grab Highly)
            ++ "\n"
            ++ "  Highly + Watch: " ++ show (grab Yes + grab Highly)
            ++ "\n"
            ++ "           Maybe: " ++ show (grab Maybe)
            ++ "\n"
            ++ "         Partial: " ++ show (grab Partial)
            ++ "\n"
            ++ "              No: " ++ show (grab No)
            ++ "\n"
            ++ "\n"
            ++ "Total Episodes Recommended: " ++ show (episodesRecommended soFar) ++ "\n"
            ++ "\n"
            ++ "----------------\n"
    where grab recc = fromJust (Map.lookup recc (numRecommended soFar))

emptyStats = Statistics 0 0 0 (foldl (\map recc -> Map.insert recc 0 map) Map.empty [Highly, Yes, Maybe, Partial, No])

stats :: Table -> Statistics
stats = foldl
        (\statsAccumulator (Doctor _ _ seasons) -> seasonsStats statsAccumulator seasons)
        emptyStats
  where
    seasonsStats :: Statistics -> [Season] -> Statistics
    seasonsStats = foldl (\statsAcc (Season _ stories) -> storiesStats statsAcc stories)
    storiesStats :: Statistics -> [Story] -> Statistics
    storiesStats = foldl
      (\soFar story -> soFar { storiesWatched = storiesWatched soFar + 1
                             , episodesWatched = episodesWatched soFar + numEpisodes story
                             , episodesRecommended =
                                 episodesRecommended soFar
                                 + case reccomendation story of
                                     Highly -> numEpisodes story
                                     Yes    -> numEpisodes story
                                     _      -> 0
                             , numRecommended = Map.update (pure . (+1)) (reccomendation story) (numRecommended soFar)
                             })



-- Commands that can be run
data Command = Output Compact | AddDoctor | AddSeason | AddStory | Usage | Stats

-- | Run a command on a table
run :: Command -> Maybe FilePath -> Table -> IO ()
run Usage _ _       = do args <- getArgs
                         putStrLn $ "did not recognise args: " ++ concat args
                         putStrLn "dw Output     : outputs the table as html"
                         putStrLn "dw add doctor : adds a new doctor to the table"
                         putStrLn "dw add season : adds a new season to the table (interactive)"
                         putStrLn "dw add story  : adds a new story to the table (interactive)"
                         putStrLn "dw stats      : print stats"
                         putStrLn ""
                         putStrLn $ "The table is stored in " ++ file
run (Output compact) Nothing table           = putStrLn $ output compact table
run (Output compact) (Just outputFile) table = writeFile outputFile (output compact table)
run AddDoctor _ table = writeOut $ addDoctor table
run AddSeason _ table = do doctor <- prompt "To doctor: "
                           season <- prompt "Season Number: "
                           let result = addSeason (read doctor) season table
                           case result of
                             Just newTable -> writeOut newTable
                             Nothing -> print "couldn't add season!"
run AddStory _ table  = do season <- prompt "Season: "
                           name <- prompt "Name: "
                           number <- prompt "Number: "
                           numEpisodes <- prompt "Number of Episodes: "
                           missing <- prompt "Missing: "
                           recommendation <- prompt "Recommendation: "
                           note <- prompt "Note: "
                           synopsis <- prompt "Synopsis: "
                           review <- prompt "Review: "
                           let result = addStory (Story name (read number) (read numEpisodes) (read missing) (read recommendation) (readNote note) synopsis review Nothing) season table
                           case result of
                             Just newTable -> writeOut newTable
                             Nothing       -> print "couldn't add story!"
run Stats _ table = print (stats table)


realMain :: IO ()
realMain = do arg <- getArgs
              hSetBuffering stdin LineBuffering
              hSetBuffering stdout NoBuffering
              fileContent <- ByteString.readFile file
              let Right table = Yaml.decodeEither fileContent
              case arg of
                ["output", "compact"]             -> run (Output Compact) Nothing table
                ["output", "compact", outputFile] -> run (Output Compact) (Just outputFile) table
                ["output"]             -> run (Output Normal) Nothing table
                ["output", outputFile] -> run (Output Normal) (Just outputFile) table
                ["add", "doctor"]      -> run AddDoctor Nothing table
                ["add", "season"]      -> run AddSeason Nothing table
                ["add", "story"]       -> run AddStory Nothing table
                ["stats"]              -> run Stats Nothing table
                _                      -> run Usage Nothing table



-- | Prompt the user for a response
prompt :: String -> IO String
prompt text = putStr text >> getLine

file, tmpfile, backup :: FilePath
file = "DrWhoDB.yaml"
tmpfile = "DrWhoDB_tmp"
backup = "DrWhoDB.bak"


readNote :: String -> Maybe Note
readNote "" = Nothing
readNote n  = Just n


writeOut :: Table -> IO ()
writeOut table = do ByteString.writeFile tmpfile (Yaml.encode table)
                    renameFile file backup
                    renameFile tmpfile file
